﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Security.Cryptography;
using System.Text;

namespace DoNet.Rsa.Core
{
    /// <summary>
    /// RSA加密解密类
    /// </summary>
    public class RsaHelper
    {
        /*
         * OaepSHA3_256
         * OaepSHA3_384
         * OaepSHA3_512
         * 
        填充算法	最小 KeySize	最大加密数据长度
        Pkcs1	    512	            模长-11
        OaepSHA1	512	            模长-42
        OaepSHA256	1024	        模长-66
        OaepSHA384	1024	        模长-98
        OaepSHA512	2048	        模长-130
        */
        static readonly Dictionary<RSAEncryptionPadding, int> PaddingLimitDic = new Dictionary<RSAEncryptionPadding, int>()
        {
            [RSAEncryptionPadding.Pkcs1] = 11,
            [RSAEncryptionPadding.OaepSHA1] = 42,
            [RSAEncryptionPadding.OaepSHA256] = 66,
            [RSAEncryptionPadding.OaepSHA384] = 98,
            [RSAEncryptionPadding.OaepSHA512] = 130
        };

        /// <summary>
        /// RSA公钥加密
        /// </summary>
        /// <param name="data">需要加密数据</param>
        /// <param name="publicKey">公钥</param>
        /// <param name="padding">填充算法</param>
        /// <param name="encoding">字符编码 Encoding.UTF8</param>
        /// <returns></returns>
        public static string Encrypt(string data, string publicKey, RSAEncryptionPadding padding, Encoding encoding)
        {
            if (encoding == null)
            {
                encoding = Encoding.UTF8;
            }

            var dataBytes = encoding.GetBytes(data);
            if (!string.IsNullOrEmpty(publicKey))
            {
                var param = RsaKeyConvert.GetPublicKeyParam(publicKey);
                if (param != null)
                {
                    RSAParameters rsaParam = (RSAParameters)param;
                    using (RSA rsa = RSA.Create())
                    {
                        rsa.ImportParameters(rsaParam);
                        return Convert.ToBase64String(rsa.Encrypt(dataBytes, padding));
                    }
                }
            }
            return string.Empty;
        }

        /// <summary>
        /// RSA公钥加密
        /// </summary>
        /// <param name="data">需要加密数据</param>
        /// <param name="publicKey">公钥</param>
        /// <param name="padding">填充算法</param>
        /// <param name="encoding">字符编码 Encoding.UTF8</param>
        /// <returns></returns>
        public static string Encrypt(string data, string publicKey, Encoding encoding)
        {
            if (encoding == null)
            {
                encoding = Encoding.UTF8;
            }
            var dataBytes = encoding.GetBytes(data);
            return Encrypt(dataBytes, publicKey, encoding);
        }

        /// <summary>
        /// RSA公钥加密
        /// </summary>
        /// <param name="data">需要加密数据</param>
        /// <param name="publicKey">公钥</param>
        /// <param name="padding">填充算法</param>
        /// <param name="encoding">字符编码 Encoding.UTF8</param>
        /// <returns></returns>
        public static string Encrypt(byte[] dataBytes, string publicKey, Encoding encoding)
        {
            if (encoding == null)
            {
                encoding = Encoding.UTF8;
            }
            if (!string.IsNullOrEmpty(publicKey))
            {
                var param = RsaKeyConvert.GetPublicKeyParam(publicKey);
                if (param != null)
                {
                    RSAParameters rsaParam = (RSAParameters)param;
                    using (RSA rsa = RSA.Create())
                    {
                        rsa.ImportParameters(rsaParam);
                        return Convert.ToBase64String(rsa.Encrypt(dataBytes, RSAEncryptionPadding.Pkcs1));
                    }
                }
            }
            return string.Empty;
        }

        /// <summary>
        /// RSA公钥分割加密[不推荐]
        /// RSA加密不支持太大的数据。在这种情况下，应该使用对称加密，RSA用于加密对称加密的密码。
        /// </summary>
        /// <param name="data">需要加密数据</param>
        /// <param name="publicKey">公钥</param>
        /// <param name="padding">填充算法</param>
        /// <param name="encoding">字符编码 Encoding.UTF8</param>
        /// <param name="keySize"></param>
        /// <param name="connChar">加密结果链接字符</param>
        /// <returns></returns>
        public static string EncryptBigData(string data, string publicKey, RSAEncryptionPadding padding, Encoding encoding, char connChar = '$')
        {
            if (encoding == null)
            {
                encoding = Encoding.UTF8;
            }

            var dataBytes = encoding.GetBytes(data);

            if (!string.IsNullOrEmpty(publicKey))
            {
                var param = RsaKeyConvert.GetPublicKeyParam(publicKey);
                if (param != null)
                {
                    RSAParameters rsaParam = (RSAParameters)param;
                    using (RSA rsa = RSA.Create())
                    {
                        rsa.ImportParameters(rsaParam);

                        var modulusLength = rsa.KeySize / 8;
                        var splitLength = modulusLength - PaddingLimitDic[padding];

                        var splitsNumber = Convert.ToInt32(Math.Ceiling(dataBytes.Length * 1.0 / splitLength));

                        var sb = new StringBuilder();
                        var pointer = 0;
                        for (int i = 0; i < splitsNumber; i++)
                        {
                            byte[] current = pointer + splitLength < dataBytes.Length ? dataBytes.Skip(pointer).Take(splitLength).ToArray() : dataBytes.Skip(pointer).Take(dataBytes.Length - pointer).ToArray();

                            sb.Append(Convert.ToBase64String(rsa.Encrypt(current, padding)));
                            sb.Append(connChar);
                            pointer += splitLength;
                        }
                        return sb.ToString().TrimEnd(connChar);
                    }
                }
            }
            return string.Empty;
        }

        /// <summary>
        /// RSA公钥加密（无长度限制）
        /// </summary>
        /// <param name="data">需要加密数据</param>
        /// <param name="publicKey">公钥</param>
        /// <param name="padding">填充算法</param>
        /// <param name="encoding">字符编码 Encoding.UTF8</param>
        /// <returns></returns>
        public static string RsaEncrypt(string data, string publicKey, RSAEncryptionPadding padding, Encoding encoding)
        {
            string result = string.Empty;
            if (encoding == null)
            {
                encoding = Encoding.UTF8;
            }

            var dataBytes = encoding.GetBytes(data);

            if (!string.IsNullOrEmpty(publicKey))
            {
                var param = RsaKeyConvert.GetPublicKeyParam(publicKey);
                if (param != null)
                {
                    RSAParameters rsaParam = (RSAParameters)param;
                    using (RSA rsa = RSA.Create())
                    {
                        rsa.ImportParameters(rsaParam);

                        var modulusLength = rsa.KeySize / 8;
                        int bufferSize = modulusLength - PaddingLimitDic[padding];//单块最大长度
                        var buffer = new byte[bufferSize];
                        using (MemoryStream inputStream = new MemoryStream(dataBytes), outputStream = new MemoryStream())
                        {
                            while (true)//分段加密
                            {
                                int readSize = inputStream.Read(buffer, 0, bufferSize);
                                if (readSize <= 0)
                                {
                                    break;
                                }
                                var temp = new byte[readSize];
                                Array.Copy(buffer, 0, temp, 0, readSize);
                                var encryptedBytes = rsa.Encrypt(temp, padding);
                                outputStream.Write(encryptedBytes, 0, encryptedBytes.Length);
                            }
                            result = Convert.ToBase64String(outputStream.ToArray());
                        }
                    }
                }
            }
            return result;
        }

        /// <summary>
        /// RSA私钥解密
        /// </summary>
        /// <param name="data">需要解密数据</param>
        /// <param name="privateKey">私钥</param>
        /// <param name="pkcs">密钥格式 Pkcs1，Pkcs8</param>
        /// <param name="padding">填充算法</param>
        /// <param name="encoding">字符编码 Encoding.UTF8</param>
        /// <returns></returns>
        public static string Decrypt(string data, string privateKey, RsaKeyFormat pkcs, RSAEncryptionPadding padding, Encoding encoding)
        {
            if (encoding == null)
            {
                encoding = Encoding.UTF8;
            }

            var dataBytes = encoding.GetBytes(data);
            if (!string.IsNullOrEmpty(privateKey))
            {
                var param = RsaKeyConvert.GetPrivateKeyParam(privateKey, pkcs);
                if (param != null)
                {
                    RSAParameters rsaParam = (RSAParameters)param;
                    using (RSA rsa = RSA.Create())
                    {
                        rsa.ImportParameters(rsaParam);
                        return encoding.GetString(rsa.Decrypt(dataBytes, padding));
                    }
                }
            }
            return string.Empty;
        }

        /// <summary>
        /// RSA私钥解密
        /// </summary>
        /// <param name="data">需要解密数据</param>
        /// <param name="privateKey">私钥</param>
        /// <param name="padding">填充算法</param>
        /// <param name="encoding">字符编码 Encoding.UTF8</param>
        /// <returns></returns>
        public static string Decrypt(string data, string privateKey, Encoding encoding)
        {
            var dataBytes = encoding.GetBytes(data);
            return Decrypt(dataBytes, privateKey, encoding);
        }

        /// <summary>
        /// RSA私钥解密
        /// </summary>
        /// <param name="dataBytes">需要解密数据</param>
        /// <param name="privateKey">私钥</param>
        /// <param name="padding">填充算法</param>
        /// <param name="encoding">字符编码 Encoding.UTF8</param>
        /// <returns></returns>
        public static string Decrypt(byte[] dataBytes, string privateKey, Encoding encoding)
        {
            if (encoding == null)
            {
                encoding = Encoding.UTF8;
            }
            if (!string.IsNullOrEmpty(privateKey))
            {
                using (RSA rsa = RSA.Create())
                {
                    rsa.FromXmlString(RsaKeyConvert.PrivateKeyPkcsToXml(privateKey));
                    return encoding.GetString(rsa.Decrypt(dataBytes, RSAEncryptionPadding.Pkcs1));
                }
            }
            return string.Empty;
        }

        /// <summary>
        /// RSA私钥拆分解密[不推荐]
        /// RSA加密不支持太大的数据。在这种情况下，应该使用对称加密，RSA用于加密对称加密的密码。
        /// </summary>
        /// <param name="data">需要加密数据</param>
        /// <param name="privateKey">私钥</param>
        /// <param name="pkcs">密钥格式 Pkcs1，Pkcs8</param>
        /// <param name="padding">填充算法</param>
        /// <param name="encoding">字符编码 Encoding.UTF8</param>
        /// <param name="connChar">加密结果链接字符</param>
        /// <returns></returns>
        public static string DecryptBigData(string data, string privateKey, RsaKeyFormat pkcs, RSAEncryptionPadding padding, Encoding encoding, char connChar = '$')
        {
            if (encoding == null)
            {
                encoding = Encoding.UTF8;
            }

            var dataArray = data.Split(new[] { connChar }, StringSplitOptions.RemoveEmptyEntries);

            if (!string.IsNullOrEmpty(privateKey))
            {
                var param = RsaKeyConvert.GetPrivateKeyParam(privateKey, pkcs);
                if (param != null)
                {
                    RSAParameters rsaParam = (RSAParameters)param;
                    using (RSA rsa = RSA.Create())
                    {
                        rsa.ImportParameters(rsaParam);

                        var byteList = new List<byte>();
                        foreach (var item in dataArray)
                        {
                            byteList.AddRange(rsa.Decrypt(Convert.FromBase64String(item), padding));
                        }
                        return encoding.GetString(byteList.ToArray());
                    }
                }
            }
            return string.Empty;
        }
        /// <summary>
        /// RSA私钥解密（无长度限制）
        /// </summary>
        /// <param name="data">需要解密的数据</param>
        /// <param name="privateKey">私钥</param>
        /// <param name="pkcs">密钥格式 Pkcs1，Pkcs8</param>
        /// <param name="padding">填充算法</param>
        /// <param name="encoding">字符编码 Encoding.UTF8</param>
        /// <returns></returns>
        public static string RsaDecrypt(string data, string privateKey, RsaKeyFormat pkcs, RSAEncryptionPadding padding, Encoding encoding)
        {
            if (encoding == null)
            {
                encoding = Encoding.UTF8;
            }

            byte[] dataBytes = Convert.FromBase64String(data);

            if (!string.IsNullOrEmpty(privateKey))
            {

                var param = RsaKeyConvert.GetPrivateKeyParam(privateKey, pkcs);
                if (param != null)
                {
                    RSAParameters rsaParam = (RSAParameters)param;
                    using (RSA rsa = RSA.Create())
                    {
                        rsa.ImportParameters(rsaParam);

                        int bufferSize = rsa.KeySize / 8;
                        var buffer = new byte[bufferSize];
                        using (MemoryStream inputStream = new MemoryStream(dataBytes), outputStream = new MemoryStream())
                        {
                            while (true)
                            {
                                int readSize = inputStream.Read(buffer, 0, bufferSize);
                                if (readSize <= 0)
                                {
                                    break;
                                }
                                var temp = new byte[readSize];
                                Array.Copy(buffer, 0, temp, 0, readSize);
                                var rawBytes = rsa.Decrypt(temp, padding);
                                outputStream.Write(rawBytes, 0, rawBytes.Length);
                            }
                            return encoding.GetString(outputStream.ToArray());
                        }
                    }
                }
            }
            return string.Empty;
        }

        /// <summary>
        /// 使用私钥进行数据签名
        /// </summary>
        /// <param name="data">需要验证签名数据</param>
        /// <param name="privateKey">私钥</param>
        /// <param name="pkcs">密钥格式 Pkcs1，Pkcs8</param>
        /// <param name="hashAlgorithmName">签名哈希算法</param>
        /// <param name="padding">签名填充算法</param>
        /// <param name="encoding">字符编码 Encoding.UTF8</param>
        /// <returns></returns>
        public static string SignData(string data, string privateKey, RsaKeyFormat pkcs, HashAlgorithmName hashAlgorithmName, RSASignaturePadding padding, Encoding encoding)
        {
            return Convert.ToBase64String(SignDataGetBytes(data, privateKey, pkcs, hashAlgorithmName, padding, encoding));
        }

        /// <summary>
        /// 使用私钥进行数据签名
        /// </summary>
        /// <param name="data">需要验证签名数据</param>
        /// <param name="privateKey">私钥</param>
        /// <param name="pkcs">密钥格式 Pkcs1，Pkcs8</param>
        /// <param name="hashAlgorithmName">签名哈希算法</param>
        /// <param name="padding">签名填充算法</param>
        /// <param name="encoding">字符编码 Encoding.UTF8</param>
        /// <returns>Sign bytes</returns>
        public static byte[] SignDataGetBytes(string data, string privateKey, RsaKeyFormat pkcs, HashAlgorithmName hashAlgorithmName, RSASignaturePadding padding, Encoding encoding)
        {
            if (encoding == null)
            {
                encoding = Encoding.UTF8;
            }

            var dataBytes = encoding.GetBytes(data);

            var param = RsaKeyConvert.GetPrivateKeyParam(privateKey, pkcs);
            if (param != null)
            {
                RSAParameters rsaParam = (RSAParameters)param;
                using (RSA rsa = RSA.Create())
                {
                    rsa.ImportParameters(rsaParam);
                    return rsa.SignData(dataBytes, hashAlgorithmName, padding);
                }
            }
            return new byte[0];
        }

        /// <summary>
        /// 使用公钥验证数据签名
        /// </summary>
        /// <param name="data">需要验证签名数据</param>
        /// <param name="sign">签名</param>
        /// <param name="publicKey">公钥</param>
        /// <param name="hashAlgorithmName">签名哈希算法</param>
        /// <param name="padding">签名填充算法</param>
        /// <param name="encoding">字符编码 Encoding.UTF8</param>
        /// <returns></returns>
        public static bool VerifyData(string data, string sign, string publicKey, HashAlgorithmName hashAlgorithmName, RSASignaturePadding padding, Encoding encoding)
        {
            if (encoding == null)
            {
                encoding = Encoding.UTF8;
            }

            var dataBytes = encoding.GetBytes(data);
            var signBytes = Convert.FromBase64String(sign);

            if (!string.IsNullOrEmpty(publicKey))
            {
                var param = RsaKeyConvert.GetPublicKeyParam(publicKey);
                if (param != null)
                {
                    RSAParameters rsaParam = (RSAParameters)param;
                    using (RSA rsa = RSA.Create())
                    {
                        rsa.ImportParameters(rsaParam);
                        return rsa.VerifyData(dataBytes, signBytes, hashAlgorithmName, padding);
                    }
                }
            }
            return false;
        }
    }
}
